package es.duma.util.xsl;

import es.duma.util.log.Log;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

/*
 * Clase de control de las trasnforamciones xsl del sistema
 * Se tiene en cuenta el API java Trax de JAXP para implementar las transforamciones xsl, de 
 * manera que el procesador xsl concreto puede seleccionarse en tiempo de ejecucion
 */
public class TransformXSL {
    
    /** Factoria de transformadores*/
    protected static TransformerFactory _tFactory = null;

    /** Template compilada para la tranformacion */
    private Templates templateC = null;

    /** Path del xsl para la transformacion */
    private String xslPath;
    
    /** Folder donde esta la plantilla. Utilizado para incluir otras */
    private String folder;

    /** Parametros para la plantilla */
    private Hashtable parameters=null;

    /** Clase que resuelve el acceso al source de posibles plantillas incluidas */
    protected URIResolver uriresolver = null;

    /** 
     * Propiedad del sistema que determina el procesador que utilizar para la transformacion
     * en tiempo de ejecucion
     */
    protected static final String TRAX_PROPERTIE = "javax.xml.transform.TransformerFactory";
    
    /** Valor de la propiedad para la implementacion SAXON */
    public static final String SAXON = "net.sf.saxon.TransformerFactoryImpl";
    
    /** Valor de la propiedad para la implementacion de ORACLE */
    public static final String ORACLE = "oracle.xml.jaxp.JXSAXTransformerFactory";
    
    /** Valor de la propiedad para la implementacion XALAN */
    public static final String XALAN = "org.apache.xalan.processor.TransformerFactoryImpl";
    
    /** Procesador xsl utilizado por defecto ( si no se especifica uno en la configuracion ) */
    public static final String DEFAULT_XSL_ENGINE=SAXON;
    
    
    /**
     * Constructor de la clase.
     * Obtiene la plantilla compilada desde la cache o la compila si no existiera.
     * Una vez creado se invoca el metodo print con el xml que se quiere tranformar
     * @param xslPath path completo de la plantilla xsl con la que se quiere iniciar la transformacion xsl
     */
    public TransformXSL(String xslPath) {
        
        // Se crea un nuevo transformador xsl
        this.xslPath=xslPath;
        // Se obtiene el folder de la plantilla
        folder=xslPath.substring(0,xslPath.lastIndexOf("/"))+"/";
        Log.debug(this,"El folder de la plantilla es "+folder);
    
        // Crear la factoria de transformadores con el nuevo xsl engine
        try{
            setXSLengine(SAXON);
        }catch (TransformerFactoryConfigurationError e){
            Log.error(this," Error al iniciar TransformerFactory "+e);
        }
        
        templateC= compileTemplate(xslPath);
        //Se compila la plantilla si no esta en la cache
//        Templates aux=null;
//        if ((aux=CacheManager.getTemplate(xslPath))!=null){
//            templateC=aux;
//        }else{       
//            templateC= compileTemplate(xslPath);
//            CacheManager.putTemplate(templateC,xslPath);
//        }
       
    }
    
    
    /**
     * Realiza la transforamcaion xsl de la plantilla que se ha compilado en el constructor de la clase
     * y el xml pasado por el parametro in
     * @param in xml de entrada para ser transforamdo
     * @param out OutputStream para escribir el resultado de la transforamcion
     * @throws IOException
     */
    public void print(InputStream in,
                      OutputStream out) throws IOException {
        Source xmlsource = null;
        xmlsource = new StreamSource(in);
        StreamResult output = new StreamResult(out);
        transform(xmlsource, output);
        try {
            out.flush();
        } catch (IOException e) {
            throw new IOException("Error al volcar la tranformacion al outputStream " +
                                              e);
        }
    }
    
    /**
     * 
     * @param list
     * @param out
     * @throws IOException
     */
    public void printMerge(List list, OutputStream out) throws IOException {
        Source xmlsource = null;
        
        // Stream con la salida final (merge de las transformaciones documentos lista)
        StreamResult output = new StreamResult(out);
        
        // En cada posicion de list viene un documento xml a transformar en formato String
        Iterator it= list.iterator();
        byte[] bytes=null;
        String merge="<merge>";
        bytes=merge.getBytes();
        while (it.hasNext()){
            // Se obtiene el elemento de la lista
            String xml=(String)it.next();
            Log.debug(this,"Xml leido "+xml);
            // Se obtiene el InputString del String
            bytes= xml.getBytes();
        }
        
        //Cierro elementeo merge
         merge="</merge>";
         bytes=merge.getBytes();
        
        InputStream in= new ByteArrayInputStream(bytes);
         // Se obtiene el Source
        xmlsource = new StreamSource(in);
        
        
        transform(xmlsource, output);
        try {
            out.flush();
        } catch (IOException e) {
            throw new IOException("Error al volcar la tranformacion al outputStream " +
                                              e);
        }
        
    }
    
    
    /**
     * Compila la plantilla pasada por parametro en un objeto de tipo javax.xml.transform.Templates
     * que pertenece al api java Trax de JAXP
     * @param xslPath path completo de la plantilla xsl a compilar
     * @return la plantilla compilada en el objeto de la clase Templates [null si no ha sido posible compilarla]
     */
    private Templates compileTemplate(String xslPath){
        URL xslUrl=null;
        InputStream in=null;
        Source xslSource=null;
        //Se toman referencias temporales
         Long ini=System.currentTimeMillis();
        
        Log.debug(this,"Se inicia la compilacion de la plantilla xsl: "+xslPath);
        try {
            xslUrl = new URL(xslPath);
            in = xslUrl.openStream(); 
        } catch (MalformedURLException e) {
            Log.error(this,"URL mal formada " +e);
        } catch (IOException e) {
            Log.error(this,"No existe la plantilla xsl en el path indicado " +e);
        }
        
        //Crear una nueva template
        try{
            xslSource = new StreamSource(in);
            //xslSource.setSystemId(xslPath);
             //Se fija el URI resolver para las plantillas incluidas
             _tFactory.setURIResolver(new XSLuriResolver(folder));
            Log.debug(this,"Creando el objeto Template");
            templateC = _tFactory.newTemplates(xslSource);
            //Se toma el tiempo de finalizacion para obtener duracion de la compilacion xsl
            Long fin=System.currentTimeMillis();
            Log.debug(this,"Plantilla compilada en "+(fin-ini)+"ms");
            return templateC;                       
        } catch (Exception e) {
            Log.error(this,"Error al compilar la plantilla xsl " +e);
        }
          
        return null;
    }
    
    
    /**
     * Metodo privado que invoca la transforamcion xsl de la plantilla compilada indicada en el costructor
     * y  el xml de entrada indicado en el metodo print
     * @param xmlSource xml de entrada wrapeado a un objeto de la clase Source
     * @param result objeto de la clase Result para almacenar el resultado de la transforamcion
     * @throws IOException
     */
    private synchronized void transform(Source xmlSource,
                                        Result result) throws IOException {
        Transformer transformer = null;
        Log.debug(this,"Se va iniciar la tranformacion");
        if (templateC != null) {
            
            // Contruir un transformer a partir de la plantilla compilada
            try {
                transformer = templateC.newTransformer();
            } catch (TransformerConfigurationException e) {
                throw new IOException("Ha ocurrido un error al iniciar la tranformacion " +
                                                  e);
            }
            if (transformer==null){
                Log.debug(this,"El transformer es null");
            }
            // Pasar parametros a la transformacion
            if (parameters != null) {
                Log.debug(this," Fijando parametros a aplicar en la plantilla");
                Enumeration paramNames = parameters.keys();
                String paramName = null;
                String paramVal = null;
                while (paramNames.hasMoreElements()) {
                    paramName = (String)paramNames.nextElement();
                    paramVal = (String)parameters.get(paramName);
                    Log.debug(this,"Se pasa el parametro al xsl=> "+paramName+" : "+paramVal);
                    if ((paramVal != null) && (paramName!=null)) {
                        transformer.setParameter((String)paramName, (String)paramVal);
                    }
                    paramName = null;
                    paramVal = null;
                }

            }
            // Ejecutar la transformacion
            try {
                Log.debug(this," Intentando ejecutar la transformacion");
                transformer.transform(xmlSource, result);
                Log.debug(this," Transformacion finalizada corectamente");
            } catch (TransformerException e) {
                Log.error(this,"Ha ocurrido un error al ejecutar la tranformacion " +e);
                throw new IOException("A ocurrido un error al ejecutar la tranformacion " +e);
            }

        } else {
            Log.error(this,"No se puede iniciar la tranformacion sin un template compilado");
            throw new IOException("No se puede iniciar la tranformacion sin un template compilado");

        }
    }
    
    /**
     * Se especifica en motor xsl que se utiliza en la transformacion en tiempo de ejecucion
     * @param engine identificador del procesador xsl de entre los permitidos (constantes de la clase)
     */
    private void setXSLengine(String engine) {
        //Si no existe la factoria de transformadores se crea.
        if (_tFactory==null){
            // Comprobar validez del engine elegido, en caso contrario se estable el engine por defecto
            if ((engine.equals(ORACLE)) || (engine.equals(XALAN)) ||  (engine.equals(SAXON))) {
                Log.debug(this," Fijando motor XLS: "+engine);
            } else {
                Log.error(this," Error al fijar el motor XSL. Se fija el motor por defecto: "+DEFAULT_XSL_ENGINE);
            }
    
            // Set the TransformerFactory system property.
            Properties props = System.getProperties();
            props.put(TRAX_PROPERTIE, engine);
            System.setProperties(props);
            Log.debug(this," Creando factoria de transformadores");
            // Crear la factoria de transformadores con el nuevo xsl engine
            try{
                _tFactory = TransformerFactory.newInstance();
                //Se fija el URI resolver para las plantillas incluidas
                _tFactory.setURIResolver(new XSLuriResolver(folder));
            }catch (TransformerFactoryConfigurationError e){
                Log.error(this," Error al iniciar TransformerFactory "+e);
            }
        }else{
            Log.debug(this,"La factoria de transformadores ya ha sido iniicializada");
        }
    }
            

    
    /**
     * Se pasan los parametros a la plantilla xsl antes de la transforamcion.
     * Los parametros son declarados dentro de la plantilla xsl con clausulas <xsl:param></xsl:param>
     * @param parameters tabla hash con los parametros
     * @throws IOException
     */
    public void setParameters(Hashtable parameters) throws IOException {
        Log.debug(this," Fijando parametros a aplicar a la plantilla XSL");
        parameters=new Hashtable();
        if (parameters != null) {
            this.parameters = parameters;
        } else {
            throw new IOException("Los parametros que se pretenden fijar a la plantilla son nulos");
        }
    }
    
    
    /**
    * Anyade un nuevo parametro a la lista de parametros de la trasformacion
    * @param name nombre del parametro
    * @param value valor del parametro
    */
    public void addParameterList(String name, List value) {
        if (parameters==null)
            parameters=new Hashtable();        
        parameters.put(name, value);
    }

    /**
    * Anyade un nuevo parametro a la lista de parametros de la trasformacion
    * @param name nombre del parametro
    * @param value valor del parametro
    */
    public void addParameter(String name, String value) {
        if (parameters==null)
            parameters=new Hashtable();  
        parameters.put(name, value);
    }
    
    
    
}
